﻿namespace MetaProperties.Core
{
    using System;

    using MetaProperties.Framework;

    /// <summary>
    /// An observable value which is bound to a value on an object implementing IObservable&lt;T&gt;.
    /// </summary>
    /// <typeparam name="T">The type of the value.</typeparam>
    public class BoundObservable<T> : GenericBoundObservable<T>
    {
        /// <summary>
        /// The source of the binding.
        /// </summary>
        private MetaProperties.Framework.IObservable<T> source;

        /// <summary>
        /// The method to set the bound value on the source.
        /// </summary>
        private Action<T> setValueMethod;

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundObservable&lt;T&gt;"/> class.
        /// </summary>
        public BoundObservable()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundObservable&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="defaultValue">The default value.</param>
        public BoundObservable(T defaultValue)
        {
            this.DefaultValue = defaultValue;
        }

        /// <summary>
        /// Gets a value indicating whether the item is currently bound.
        /// </summary>
        /// <value><c>true</c> if the source is set; otherwise, <c>false</c>.</value>
        protected override bool IsBound
        {
            get { return this.source != null; }
        }

        /// <summary>
        /// Binds to the specified source.
        /// </summary>
        /// <param name="source">The source.</param>
        public void Bind(MetaProperties.Framework.IObservable<T> source)
        {
            this.Bind(source, null);
        }

        /// <summary>
        /// Binds to the specified source.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="setValueMethod">The set value method.</param>
        public void Bind(MetaProperties.Framework.IObservable<T> source, Action<T> setValueMethod)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            this.Unbind();

            this.source = source;
            this.setValueMethod = setValueMethod;
            this.ObservableValue.Value = this.source.Value;
            this.source.ValueChanged += this.Source_ValueChanged;

            if (this.setValueMethod != null)
            {
                this.ObservableValue.ValueChanged += this.ObservableValue_ValueChanged;
            }
        }

        /// <summary>
        /// Unbinds from the source.
        /// </summary>
        public override void Unbind()
        {
            if (this.source != null)
            {
                this.source.ValueChanged -= this.Source_ValueChanged;

                if (this.setValueMethod != null)
                {
                    this.ObservableValue.ValueChanged -= this.ObservableValue_ValueChanged;
                }

                this.source = null;
                this.setValueMethod = null;
                this.ObservableValue.Value = this.DefaultValue;
            }
        }

        /// <summary>
        /// Handles the ValueChanged event of the ObservableValue object.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs&lt;T&gt;"/> instance containing the event data.</param>
        private void ObservableValue_ValueChanged(object sender, ValueChangedEventArgs<T> e)
        {
            if (this.setValueMethod != null)
            {
                this.setValueMethod(e.NewValue);
            }
        }

        /// <summary>
        /// Handles the ValueChanged event of the source.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs&lt;T&gt;"/> instance containing the event data.</param>
        private void Source_ValueChanged(object sender, ValueChangedEventArgs<T> e)
        {
            this.ObservableValue.Value = e.NewValue;
        }
    }
}
